#include <cstdlib>
#include <random>
#include <time.h>

#include "armory.h"
#include "consumable.h"
#include "defines.h"
#include "item.h"
#include "manager.h"
#include "monster.h"
#include "tile.h"

using std::list;
using std::string;

Manager manager;
std::ranlux24 rndgen;
std::uniform_int_distribution< int > uidistr;
std::uniform_int_distribution< int > uidistr100( 0, 99 );
std::uniform_int_distribution< int > uidistr20( 0, 19 );

void initRand()
{
	rndgen.seed( ( unsigned long ) time( 0 ) );
}

int mrand()
{
	return uidistr( rndgen );
}

int mrand( int x )
{
	return uidistr( rndgen ) % x;
}

Armor * isArmor( MapObject * mobj )
{
	if( mobj->getType().armor )
		return reinterpret_cast< Armor * >( mobj );
	return nullptr;
}

Weapon * isWeapon( MapObject * mobj )
{
	if( mobj->getType().weapon )
		return reinterpret_cast< Weapon * >( mobj );
	return nullptr;
}

Item * isItem( MapObject * mobj )
{
	if( mobj->getType().item )
		return reinterpret_cast< Item * >( mobj );
	return nullptr;
}

Monster * isMonster( MapObject * mobj )
{
	if( mobj->getType().monster )
		return reinterpret_cast< Monster * >( mobj );
	return nullptr;
}

Consumable * isConsumable( MapObject * mobj )
{
	if( mobj->getType().consumable )
		return reinterpret_cast< Consumable * >( mobj );
	return nullptr;
}

void fillScreen( char filler, int y0, int x0, int rows, int columns )
{
	if( y0 < 0 )
		y0 = getbegy( stdscr );
	if( x0 < 0 )
		x0 = getbegx( stdscr );
	if( rows < 0 )
		rows = getmaxy( stdscr );
	if( columns < 0 )
		columns = getmaxx( stdscr );
	for( int i = y0; i < y0 + rows; i++ )
	{
		for( int j = x0; j < x0 + columns; j++ )
		{
			mvaddch( i, j, filler );
		}
	}
}

void fillWScreen( WINDOW * wind, char filler, int y0, int x0, int rows, int columns )
{
	if( y0 < 0 )
		y0 = getbegy( wind );
	if( x0 < 0 )
		x0 = getbegx( wind );
	if( rows < 0 )
		rows = getmaxy( wind );
	if( columns < 0 )
		columns = getmaxx( wind );
	for( int i = y0; i < y0 + rows; i++ )
	{
		for( int j = x0; j < x0 + columns; j++ )
		{
			mvwaddch( wind, i, j, filler );
		}
	}
}

void deleteScreen( WINDOW * wind )
{
	/*int y0 = getbegy( wind );
	int x0 = getbegx( wind );
	int y1 = getmaxy( wind );
	int x1 = getmaxx( wind );

	for( int i = y0; i < y1; i++ )
	{
		for( int j = x0; j < x1; j++ )
		{
			mvwaddch( wind, i, j, ' ' );
		}
	}*/
	fillWScreen( wind );
	wrefresh( wind );
	delwin( wind );
}

WINDOW * newScreen( int height, int width, int y0, int x0 )
{
	WINDOW * w = newwin( height, width, y0, x0 );
	keypad( w, TRUE );
	scrollok( w, TRUE );
	return w;
}

int getXAdj( int direction )
{
	switch( direction )
	{
	case DIR_N:
		return 0;
	case DIR_E:
		return 1;
	case DIR_S:
		return 0;
	case DIR_W:
		return -1;
	case DIR_NE:
		return 1;
	case DIR_SE:
		return 1;
	case DIR_NW:
		return -1;
	case DIR_SW:
		return -1;
		break;
	default:
		return 0;
	}
}

int getYAdj( int direction )
{
	switch( direction )
	{
	case DIR_N:
		return -1;
	case DIR_E:
		return 0;
	case DIR_S:
		return 1;
	case DIR_W:
		return 0;
	case DIR_NE:
		return -1;
		break;
	case DIR_SE:
		return 1;
		break;
	case DIR_NW:
		return -1;
		break;
	case DIR_SW:
		return 1;
		break;
	default:
		return 0;
	}
}

void getAdj( int direction, int & x, int & y )
{
	x = 0;
	y = 0;
	switch( direction )
	{
	case DIR_N:
		y--;
		break;
	case DIR_E:
		x++;
		break;
	case DIR_S:
		y++;
		break;
	case DIR_W:
		x--;
		break;
	case DIR_NE:
		y--;
		x++;
		break;
	case DIR_SE:
		y++;
		x++;
		break;
	case DIR_NW:
		y--;
		x--;
		break;
	case DIR_SW:
		y++;
		x--;
		break;
	}
}

int getKeyDir( int key )
{
	switch( key )
	{
	case KEY_NUM1:
		return DIR_SW;
	case KEY_NUM2:
	case KEY_DOWN:
		return DIR_S;
	case KEY_NUM3:
		return DIR_SE;
	case KEY_NUM4:
	case KEY_LEFT:
		return DIR_W;
	case KEY_NUM6:
	case KEY_RIGHT:
		return DIR_E;
	case KEY_NUM7:
		return DIR_NW;
	case KEY_NUM8:
	case KEY_UP:
		return DIR_N;
	case KEY_NUM9:
		return DIR_NE;
	default:
		return -1;
	}
}

bool prob( int x )
{
	if( uidistr100( rndgen ) < x )
		return true;
	else
		return false;
}

string numToString( int x )
{
	string result;
	if( x == 0 )
	{
		result.push_back( 48 );
		return result;
	}
	int y = 10;
	while( x / y )
	{
		y *= 10;
	}
	y /= 10;
	while( y != 0 && x / y )
	{
		char ch = x / y % 10 + 48;
		result.push_back( ch );
		y /= 10;
	}
	return result;
}

void delay( int ms )
{
	int start = clock();
	while( ( start + ms ) > clock() )
		;
}

bool isMovKey( int key )
{
	switch( key )
	{
	case KEY_NUM1:
	case KEY_NUM2:
	case KEY_NUM3:
	case KEY_NUM4:
	case KEY_NUM6:
	case KEY_NUM7:
	case KEY_NUM8:
	case KEY_NUM9:
	case KEY_UP:
	case KEY_DOWN:
	case KEY_LEFT:
	case KEY_RIGHT:
		return true;
	default:
		return false;
	}
}

void drawLine( int x0, int x1, int y0, int y1 )
{
	int dX = x1 - x0;
	int dY = y1 - y0;
	if( dX < 0 )
		dX = -dX;
	if( dY < 0 )
		dY = -dY;
	int sX = 1;
	int sY = 1;
	if( x0 > x1 )
		sX = -1;
	if( y0 > y1 )
		sY = -1;
	int err = dX - dY;
	while( 1 )
	{
		mvaddch( x0, y0, '*' | A_BOLD );
		if( x0 == x1 && y0 == y1 )
			break;
		int e2 = err * 2;
		if( e2 > - dY )
		{
			err -= dY;
			x0 += sX;
		}
		if( e2 < dX )
		{
			err += dX;
			y0 += sY;
		}
	}
}

//DFRPG helper functions

int d20()
{
	return uidistr20( rndgen ) + 1;
}

// helpers

Monster * spawnMonster( monster_t baseMonster, int x, int y )
{
	return new Monster( baseMonster, x, y );
}

Armor * spawnArmor( armor_t baseArmor, int x, int y )
{
	return new Armor( baseArmor, x, y );
}

Weapon * spawnWeapon( weapon_t baseWeapon, int x, int y )
{
	return new Weapon( baseWeapon, x, y );
}

Consumable * spawnConsumable( consumable_t baseConsumable, int x, int y )
{
	return new Consumable( baseConsumable, x, y );
}

Monster * spawnMonster( monster_t baseMonster, MapObject * mobj )
{
	return spawnMonster( baseMonster, mobj->getX(), mobj->getY() );
}

Armor * spawnArmor( armor_t baseArmor, MapObject * mobj )
{
	return spawnArmor( baseArmor, mobj->getX(), mobj->getY() );
}

Weapon * spawnWeapon( weapon_t baseWeapon, MapObject * mobj )
{
	return spawnWeapon( baseWeapon, mobj->getX(), mobj->getY() );
}

Consumable * spawnConsumable( consumable_t baseConsumable, MapObject * mobj )
{
	return spawnConsumable( baseConsumable, mobj->getX(), mobj->getY() );
}

Tile * mapTileAt( map * m, int x, int y )
{
	if( x < 0 || y < 0 || x >= MAP_X || y >= MAP_Y )
		return nullptr;
	return m->mapTiles[ x ][ y ];
}

inline Tile * mapTileAt( map * m, const MapObject * mobj )
{
	return mapTileAt( m, mobj->getX(), mobj->getY() );
}

Tile * tileAt( int x, int y )
{
	if( x < 0 || y < 0 || x >= MAP_X || y >= MAP_Y )
		return nullptr;
	return manager.getMTiles()[ x ][ y ];
}

inline Tile * tileAt( const MapObject * mobj )
{
	return tileAt( mobj->getX(), mobj->getY() );
}

int getDistance( const Tile * start, const Tile * end )
{
	if( start == nullptr || end == nullptr
		|| !start->isValidLoc() || !end->isValidLoc() )
		return -1;
	if( start == end )
		return 0;
	int dx = abs( start->getX() - end->getX() );
	int dy = abs( start->getY() - end->getY() );
	return dx > dy ? dx : dy;
}

inline int getDistance( const MapObject * start, const MapObject * end )
{
	return getDistance( tileAt( start ), tileAt( end ) );
}

int getDir( int x0, int y0, int x1, int y1 )
{
	if( x0 < 0 || x0 >= MAP_X )
		return 0;
	if( y0 < 0 || y0 >= MAP_Y )
		return 0;
	if( x1 < 0 || x0 >= MAP_X )
		return 0;
	if( y1 < 0 || y1 >= MAP_Y )
		return 0;
	int dX = x0 - x1;
	int dY = y0 - y1;
	if( dX == 0 )
	{
		if( dY == 0 )
			return 0;
		else if( dY > 0 )//up
			return DIR_N;
		else//if( dY < 0 )//down
			return DIR_S;
	}
	else if( dX > 0 )//left
	{
		if( dY == 0 )
			return DIR_W;
		else if( dY > 0 )//up
			return DIR_NW;
		else//( dY < 0 )//down
			return DIR_SW;
	}
	else// if( dX < 0 )//right
	{
		if( dY == 0 )
			return DIR_E;
		else if( dY > 0 )//up
			return DIR_NE;
		else//( dY < 0 )//down
			return DIR_SE;
	}
}

inline int getDir( const MapObject * start, const MapObject * end )
{
	return getDir( start->getX(), start->getY(), end->getX(), end->getY() );
}

/*
###         ###
### HELPERS ###
###         ###
*/

Monster * getLiveMonster( const Tile * t )
{
	if( !t )
		return nullptr;
	for( auto iter = t->monsterContents.begin();
		iter != t->monsterContents.end(); iter++ )
	{
		Monster * m = *iter;
		if( !m->isDead() )
			return m;
	}
	return nullptr;
}

Monster * getLiveMonster( const MapObject * mobj, int direction )
{
	return getLiveMonster( tileAt( mobj->getX() + getXAdj( direction ),
		mobj->getY() + getYAdj( direction ) ) );
}

inline Monster * getLiveMonster( int x, int y )
{
	return getLiveMonster( tileAt( x, y ) );
}

list< Tile * > getTilesRad( const MapObject * center, int radius )
{
	list< Tile * > tilesInRad;
	for( int i = -radius; i <= radius; i++ )
		for( int j = -radius; j <= radius; j++ )
		{
			Tile * t = tileAt( center->getX() + i, center->getY() + j );
			if( !t || t && i != 1 && i != -1 && j != 1 && j != -1
				&& ( pow( ( float ) abs( t->getX() - center->getX() ), 2 ) )
				+pow( ( float ) abs( t->getY() - center->getY() ), 2 )
				> pow( ( float ) radius, 2 ) )
				continue;
			tilesInRad.push_back( t );
		}
	return tilesInRad;
}
